package meterMonitor.framework.aop;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import meterMonitor.consts.ResponseCode;
import meterMonitor.framework.aop.annotation.ControllerMarker;
import meterMonitor.framework.aop.annotation.ServiceMarker;
import meterMonitor.util.EmailBiz;
import meterMonitor.util.ResourceConfig;
import meterMonitor.vo.BaseResponseData;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.ObjectMapper; 

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;

/**
 * @ClassName: BizAspect
 * @Description: AOP切面类
 * @author: zhenxing.li1
 * @date: 2016年9月2日 下午5:56:39
 */
@Aspect
@Component
public class BizAspect implements InitializingBean { 
	@Autowired
	protected EmailBiz emailBiz;
	@Autowired
	protected ResourceConfig resourceConfig;
	@Autowired
	TaskExecutor taskExecutor;
	private static final Logger logger = LoggerFactory.getLogger(BizAspect.class);
	protected static final Long BUSINESS_TYPE_CONTROLLER = 601L;
	private final Set<String> baseTypes = new HashSet<>();
	private final Set<String> complexTypes = new HashSet<>();
	private final Set<String> excludedUrls = new HashSet<>();
	private static final ObjectMapper jsonMapper = new ObjectMapper();
	private static final String LOCAL_HOST_IP_V4 = "127.0.0.1";
	private static final String LOCAL_HOST_IP_V6 = "0:0:0:0:0:0:0:1";
	static {
		jsonMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		baseTypes.add("java.lang.Integer");
		baseTypes.add("java.lang.Float");
		baseTypes.add("java.lang.Long");
		baseTypes.add("java.lang.Short");
		baseTypes.add("java.lang.Byte");
		baseTypes.add("java.lang.Boolean");
		baseTypes.add("java.lang.Char");
		baseTypes.add("java.lang.String");
		baseTypes.add("java.math.BigDecimal");
		baseTypes.add("java.math.BigInteger");
		baseTypes.add("int");
		baseTypes.add("double");
		baseTypes.add("long");
		baseTypes.add("short");
		baseTypes.add("byte");
		baseTypes.add("boolean");
		baseTypes.add("char");
		baseTypes.add("float");
		complexTypes.add("org.springframework.http.HttpMethod");
		complexTypes.add("org.springframework.http.HttpEntity");
	}
	/**
	 * @Title: crossSysCommunicationAspect
	 * @Description: 调用外部接口切入点
	 * @param pjp
	 * @return
	 * @return: Object
	 * @throws Throwable
	 */
	@Around("execution(public * org.springframework.web.client.RestTemplate.postFor*(..))")
	public Object restTempatePost(ProceedingJoinPoint pjp) throws Throwable {
		return doAop(pjp);
	}
	@Around("execution(public * org.springframework.web.client.RestTemplate.getFor*(..))")
	public Object restTempateGet(ProceedingJoinPoint pjp) throws Throwable {
		return doAop(pjp);
	}
	@Around("execution(public * org.springframework.web.client.RestTemplate.exchange*(..))")
	public Object restTempateExchange(ProceedingJoinPoint pjp) throws Throwable {
		return doAop(pjp);
	}
	private Object doAop(ProceedingJoinPoint pjp) throws Throwable {
		Object returnVal = null;
		Long startTime = System.currentTimeMillis();
		String args = "";
		//Long status = SendRecordStatus.FAIL.getCode();
		String responseStr = "";
		Class<?> clazz = pjp.getTarget().getClass();
		String clazzName = clazz.getSimpleName();
		String methodName = pjp.getSignature().getName();
		String tag = concat("AOP >>>> [", clazzName, ".", methodName, "] ");
		try {
			String[] paramNames = getArgsNames(this.getClass(), getMethod(pjp), clazz.getName(), methodName);
			args = format(paramNames, pjp);
			logger.info(concat(tag, "AOP start..."));
			logger.info(concat(tag, "请求参数: ", args));
			returnVal = pjp.proceed();
			responseStr = toJson(returnVal);
			logger.info(concat(tag, "请求返回: ", responseStr));
			//status = SendRecordStatus.SUCCESS.getCode();
		} finally {
			String url = getUrl(pjp);
//			if (!excludedUrls.contains(url)) {
//				sendingRecordBiz.addRecordAsyn(clazzName + "." + methodName, url, args, IntefaceTypeEnum.Send.getCode(),
//						status, -1L, responseStr, "", "");
//			}
			logger.info(concat(tag, "方法调用耗时: ", System.currentTimeMillis() - startTime, " ms, AOP end."));
		}
		return returnVal;
	}
	private static String concat(Object... strings) {
		return StringUtils.join(strings);
	}
	private String getUrl(JoinPoint joinPoint) {
		String url = "";
		try {
			url = String.valueOf(joinPoint.getArgs()[0]);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
		return url;
	}
	private static String toJson(Object value) {
		String returnVal;
		try {
			returnVal = jsonMapper.writeValueAsString(value);
		} catch (Exception e) {
			returnVal = String.valueOf(value);
			logger.error(concat("将对象转换为JSON字符串异常:", returnVal));
		}
		return returnVal;
	}

	/**
	 * @Description: 获取被拦截的方法
	 * @date: 2016年12月23日 下午3:50:14
	 * @return: Method
	 * @param pjp
	 * @return
	 */
	private static Method getMethod(ProceedingJoinPoint pjp) {
		try {
			MethodInvocationProceedingJoinPoint mpj = (MethodInvocationProceedingJoinPoint) pjp;
			Field field = mpj.getClass().getDeclaredField("methodInvocation");
			field.setAccessible(true);
			MethodInvocation mi = (MethodInvocation) field.get(mpj);
			Method m = mi.getMethod();
			m.getParameterTypes();
			return m;
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
		return null;
	}
	
	/**
	 * @Title: controllerAspect
	 * @Description: Controller切入点
	 * @return: void
	 */
	@Pointcut("@annotation(org.springframework.stereotype.Controller)")
	public void controllerAnnotationAspect() {
		// stub
	}
	/**
	 * @Title: aroundControllerMethodInvoked
	 * @Description: around切面用于记录请求参数,返回结果,请求耗时等
	 * @param pjp
	 * @throws Throwable
	 * @return: Object
	 */
	@Around("execution(public * test.smvc.t2.controller.*.*(..))")
	public Object aroundControllerMethodInvoked(ProceedingJoinPoint pjp) throws Throwable {
		Object returnVal = null;
		Long startTime = System.currentTimeMillis();
		String ip = "";
		String args = "";
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
				.getRequest();
		Class<?> clazz = pjp.getTarget().getClass();
		String clazzName = clazz.getSimpleName();
		String methodName = pjp.getSignature().getName();
		String tag = concat("AOP >>>> [", clazzName, ".", pjp.getSignature().getName(), "] ");
		//OperatorVo currentUser = SessionContainer.getSession();
		String userName = "";
		Long userId = -1L;
//		if (null != currentUser) {
//			userName = currentUser.getOperatorName();
//			userId = currentUser.getOperatorId();
//		}
		try {
			String[] paramNames = getArgsNames(this.getClass(), getMethod(pjp), clazz.getName(), methodName);
			args = format(paramNames, pjp);
			logger.info(concat(tag, "AOP start..."));
			logger.info(concat(tag, "请求URI: ", request.getRequestURI()));
			logger.info(concat(tag, "请求参数: ", args));
			ip = getIpAddr(request);
			logger.info(concat(tag, "请求IP: ", ip));
			logger.info(concat(tag, "当前用户: ", userName));
			logger.info(concat(tag, "请求cookies: ", toJson(request.getCookies())));
			returnVal = pjp.proceed();
			logger.info(concat(tag, "请求返回: ", toJson(returnVal)));
		} catch (Exception e) {
			logger.info(concat(tag, "捕获异常: ", e.getMessage()), e);
			setErrorMessage(request, e);
			String title = concat(clazzName, ".", methodName);
			emailBiz.sendMailAsyn(title, args, e);
		//	logErrorBize.addLogErrorAsyn(BUSINESS_TYPE_CONTROLLER, request.getRequestURI(), title,
				//	ErrorTypeEnum.RuntimeException.getCode(), e, userId, userName, ip, "");
			returnVal = genErrorResponse(pjp, e);
		} finally {
			logger.info(concat(tag, "方法调用耗时: ", System.currentTimeMillis() - startTime, " ms, AOP end."));
		}
		return returnVal;
	}
	private Object genErrorResponse(ProceedingJoinPoint pjp, Exception e) throws Exception {
		ResponseBody anno = getMethodAnnotation(pjp, ResponseBody.class);
		if (anno != null) {
			BaseResponseData resp = new BaseResponseData();
			resp.setCode(ResponseCode.SYSTEM_EXCEPTION);
			resp.setMessage("系统异常");
			return resp;
		} else {
			throw e;
		}
	}
	private void setErrorMessage(HttpServletRequest request, Throwable e) {
		request.setAttribute("_isError", true);
		request.setAttribute("_errorMsg", e.getMessage());
	}
	private String format(String[] paramNames, JoinPoint joinPoint) {
		Object[] args = joinPoint.getArgs();
		StringBuilder sb = new StringBuilder();
		for (int k = 0; k < args.length; k++) {
			Object arg = args[k];
			if (null != arg) {
				sb.append(paramNames[k]).append(" ");
				// 获取对象类型
				Class<?> clazz = arg.getClass();
				String typeName = clazz.getName();
				if (baseTypes.contains(typeName)) {
					sb.append("= ").append(arg).append(", ");
				} else if (complexTypes.contains(typeName)
						|| (!typeName.startsWith("javax") && !typeName.startsWith("org"))) {
					sb.append("= ").append(toJson(arg)).append(", ");
				} else {
					sb.append("= ").append(typeName).append(", ");
				}
			}
		}
		return sb.toString();
	}
	/**
	 * 得到方法参数的名称
	 * @param cls
	 * @param clazzName
	 * @param methodName
	 * @return
	 * @throws NotFoundException
	 */
	private static String[] getArgsNames(Class<?> cls, Method method, String clazzName, String methodName) throws NotFoundException {
		ClassPool pool = ClassPool.getDefault();
		ClassClassPath classPath = new ClassClassPath(cls);
		pool.insertClassPath(classPath);
		CtClass cc = pool.get(clazzName);
		CtMethod cm = cc.getDeclaredMethod(methodName, getArgTypes(pool, method));
		MethodInfo methodInfo = cm.getMethodInfo();
		CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
		LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
		String[] paramNames = new String[cm.getParameterTypes().length];
		if (attr != null) {
			int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
			for (int i = 0; i < paramNames.length; i++) {
				paramNames[i] = attr.variableName(i + pos); // paramNames即参数名
			}
		}
		return paramNames;
	}
	
	private static CtClass[] getArgTypes(ClassPool pool, Method method) throws NotFoundException {
		Class<?>[] argTypes = method.getParameterTypes();
		CtClass[] returnVal = new CtClass[argTypes.length];
		for (int i = 0; i < argTypes.length; i++) {
			Class<?> clz = argTypes[i];
			returnVal[i] = pool.get(clz.getName());
		}
		return returnVal;
	}
	
	private String getIpAddr(HttpServletRequest request) {
		String xForwardedFor = request.getHeader("x-forwarded-for");
		String proxyClientIp = request.getHeader("Proxy-Client-IP");
		String wlProxyClientIp = request.getHeader("WL-Proxy-Client-IP");
		String ipAddress = getFirstValid(xForwardedFor, proxyClientIp, wlProxyClientIp);
		if (StringUtils.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
			ipAddress = request.getRemoteAddr();
			if (LOCAL_HOST_IP_V4.equals(ipAddress) || LOCAL_HOST_IP_V6.equals(ipAddress)) {
				// 根据网卡取本机配置的IP
				InetAddress inet = null;
				try {
					inet = InetAddress.getLocalHost();
					ipAddress = inet.getHostAddress();
				} catch (UnknownHostException e) {
					logger.error("未知主机异常", e);
				}
			}
		}
		// 对于通过多个代理的情况，第一个IP为客户端真实IP,多个IP按照','分割
		if (ipAddress != null && ipAddress.length() > 15 && ipAddress.indexOf(',') != -1) {
			// "***.***.***.***".length() = 15
			ipAddress = ipAddress.substring(0, ipAddress.indexOf(','));
		}
		return ipAddress;
	}
	private static String getFirstValid(String... strs) {
		String returnStr = "";
		for (String string : strs) {
			if (StringUtils.isNotBlank(string) && !"unknown".equals(string)) {
				returnStr = string;
				break;
			}
		}
		return returnStr;
	}
	/**
	 * 获取注解中对方法的描述信息 用于service层注解
	 * @param joinPoint 切点
	 * @return 方法描述
	 * @throws Exception
	 */
	public static String getServiceMthodDescription(JoinPoint joinPoint) throws Exception {
		String targetName = joinPoint.getTarget().getClass().getName();
		String methodName = joinPoint.getSignature().getName();
		Object[] arguments = joinPoint.getArgs();
		Class<?> targetClass = Class.forName(targetName);
		Method[] methods = targetClass.getMethods();
		String description = "";
		for (Method method : methods) {
			if (method.getName().equals(methodName)) {
				Class<?>[] clazzs = method.getParameterTypes();
				if (clazzs.length == arguments.length) {
					description = method.getAnnotation(ServiceMarker.class).bizDesc();
					break;
				}
			}
		}
		return description;
	}
	/**
	 * 获取注解中对方法的描述信息 用于Controller层注解
	 * @param joinPoint 切点
	 * @return 方法描述
	 * @throws Exception
	 */
	public static String getControllerMethodDescription(JoinPoint joinPoint) {
		String description = "";
		try {
			Class<?> clazz = joinPoint.getTarget().getClass();
			String methodName = joinPoint.getSignature().toLongString();
			Method[] methods = clazz.getMethods();
			for (Method method : methods) {
				if (method.toGenericString().startsWith(methodName)) {
					description = method.getAnnotation(ControllerMarker.class).bizDesc();
				}
			}
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
		return description;
	}
	private <T extends Annotation> T getMethodAnnotation(JoinPoint joinPoint, Class<T> annotationClass) {
		T marker = null;
		try {
			Class<?> clazz = joinPoint.getTarget().getClass();
			String methodName = joinPoint.getSignature().toLongString();
			Method[] methods = clazz.getMethods();
			for (Method method : methods) {
				if (method.toGenericString().startsWith(methodName)) {
					marker = method.getAnnotation(annotationClass);
				}
			}
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
		return marker;
	}
}
